Inspect Docker Image to Reduce Build Size

Frontend build

My Dockerfile for the frontend NextJS is very optimized for small build size outputting to the standalone feature

Frontend Dockerfile

ARG PRODUCTION_PLATFORM
FROM --platform=$PRODUCTION_PLATFORM node:20-alpine AS base
RUN echo '#### LETS GET STARTED HEYYY ####'

FROM base AS builder
# # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app

# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
# COPY keystone.ts schema.* session.ts envs.ts ./
# COPY src/keystone/schema.ts ./src/keystone/schema.ts
COPY . ./

RUN \
  if [ -f yarn.lock ]; then yarn install --frozen-lockfile; \
  elif [ -f package-lock.json ]; then npm ci; \
  elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \
  else echo "Lockfile not found." && exit 1; \
  fi
RUN yarn add sharp
RUN yarn next telemetry disable

# Learn more here: https://nextjs.org/telemetry
ENV NEXT_TELEMETRY_DISABLED 1

RUN \
  if [ -f yarn.lock ]; then yarn run build; \
  elif [ -f package-lock.json ]; then npm run build; \
  elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \
  else echo "Lockfile not found." && exit 1; \
  fi
RUN yarn next telemetry disable

# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NEXT_TELEMETRY_DISABLED 1

ENV NODE_ENV production
# Uncomment the following line in case you want to disable telemetry during runtime.

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public
# COPY --from=builder /app/.keystone ./.keystone
# COPY --from=builder /app/schema.prisma ./schema.prisma

# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next

# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

ENV PORT 3000

# server.js is created by next build from the standalone output
# https://nextjs.org/docs/pages/api-reference/next-config-js/output
CMD HOSTNAME="0.0.0.0" node server.js

Frontend end Docker Image inspected

## run this command to get below output
docker history myapp-frontend

IMAGE          CREATED          CREATED BY                                      SIZE      COMMENT
5c6aa2e91e8b   16 minutes ago   CMD ["/bin/sh" "-c" "HOSTNAME=\"0.0.0.0\" no…   0B        buildkit.dockerfile.v0
<missing>      16 minutes ago   ENV PORT=3000                                   0B        buildkit.dockerfile.v0
<missing>      16 minutes ago   EXPOSE map[3000/tcp:{}]                         0B        buildkit.dockerfile.v0
<missing>      16 minutes ago   USER nextjs                                     0B        buildkit.dockerfile.v0
<missing>      16 minutes ago   COPY --chown=nextjs:nodejs /app/.next/static…   2.57MB    buildkit.dockerfile.v0
<missing>      16 minutes ago   COPY --chown=nextjs:nodejs /app/.next/standa…   154MB     buildkit.dockerfile.v0
<missing>      16 minutes ago   RUN /bin/sh -c chown nextjs:nodejs .next # b…   0B        buildkit.dockerfile.v0
<missing>      16 minutes ago   RUN /bin/sh -c mkdir .next # buildkit           0B        buildkit.dockerfile.v0
<missing>      16 minutes ago   COPY /app/public ./public # buildkit            163kB     buildkit.dockerfile.v0
<missing>      18 minutes ago   RUN /bin/sh -c adduser --system --uid 1001 n…   3.25kB    buildkit.dockerfile.v0
<missing>      18 minutes ago   RUN /bin/sh -c addgroup --system --gid 1001 …   1.07kB    buildkit.dockerfile.v0
<missing>      18 minutes ago   ENV NODE_ENV=production                         0B        buildkit.dockerfile.v0
<missing>      18 minutes ago   ENV NEXT_TELEMETRY_DISABLED=1                   0B        buildkit.dockerfile.v0
<missing>      18 minutes ago   WORKDIR /app                                    0B        buildkit.dockerfile.v0
<missing>      18 minutes ago   RUN /bin/sh -c echo '#### LETS GET STARTED H…   0B        buildkit.dockerfile.v0
<missing>      8 days ago       CMD ["node"]                                    0B        buildkit.dockerfile.v0
<missing>      8 days ago       ENTRYPOINT ["docker-entrypoint.sh"]             0B        buildkit.dockerfile.v0
<missing>      8 days ago       COPY docker-entrypoint.sh /usr/local/bin/ # …   388B      buildkit.dockerfile.v0
<missing>      8 days ago       RUN /bin/sh -c apk add --no-cache --virtual …   5.59MB    buildkit.dockerfile.v0
<missing>      8 days ago       ENV YARN_VERSION=1.22.22                        0B        buildkit.dockerfile.v0
<missing>      8 days ago       RUN /bin/sh -c addgroup -g 1000 node     && …   118MB     buildkit.dockerfile.v0
<missing>      8 days ago       ENV NODE_VERSION=20.16.0                        0B        buildkit.dockerfile.v0
<missing>      10 days ago      /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B        
<missing>      10 days ago      /bin/sh -c #(nop) ADD file:a71f7e9bc66668361…   8.83MB  

Backend build

My backend build of a KeystoneJS app outputs to an eye watering 5gb image.

Backend Dockerfile

ARG PRODUCTION_PLATFORM
FROM --platform=$PRODUCTION_PLATFORM node:20-alpine as builder
WORKDIR /app

COPY package.json yarn.lock ./
COPY . .

RUN yarn install --production --frozen-lockfile
RUN yarn generate
RUN yarn ks:build
RUN yarn next telemetry disable
# Learn more here: https://nextjs.org/telemetry
ENV NEXT_TELEMETRY_DISABLED 1

FROM builder as runner
WORKDIR /app

COPY --from=builder /app/package.json .
# COPY --from=builder /app/yarn.lock .
# COPY --from=builder /app/.env .

COPY --from=builder /app/.keystone ./.keystone
# COPY --from=builder /app/schema.prisma ./schema.prisma
# COPY --from=builder /app/next.config.mjs ./
## todo try getting rid of `public` folder?
# COPY --from=builder /app/public ./public
## todo why is this image build to +5gb size?
# COPY --from=builder /app/node_modules ./node_modules

EXPOSE 3001
ENV NODE_ENV production
ENV PORT 3001

CMD ["yarn", "ks:start"]

Backend Docker Image inspected

docker history ksnext14-kypn-backend 
IMAGE          CREATED          CREATED BY                                      SIZE      COMMENT
17c95e78a409   6 minutes ago    CMD ["yarn" "ks:start"]                         0B        buildkit.dockerfile.v0
<missing>      6 minutes ago    ENV PORT=3001                                   0B        buildkit.dockerfile.v0
<missing>      6 minutes ago    ENV NODE_ENV=production                         0B        buildkit.dockerfile.v0
<missing>      6 minutes ago    EXPOSE map[3001/tcp:{}]                         0B        buildkit.dockerfile.v0
<missing>      6 minutes ago    COPY /app/.keystone ./.keystone # buildkit      125MB     buildkit.dockerfile.v0
<missing>      6 minutes ago    COPY /app/package.json . # buildkit             1.4kB     buildkit.dockerfile.v0
<missing>      6 minutes ago    WORKDIR /app                                    0B        buildkit.dockerfile.v0
<missing>      6 minutes ago    ENV NEXT_TELEMETRY_DISABLED=1                   0B        buildkit.dockerfile.v0
<missing>      6 minutes ago    RUN /bin/sh -c yarn next telemetry disable #…   2.2MB     buildkit.dockerfile.v0
<missing>      6 minutes ago    RUN /bin/sh -c yarn ks:build # buildkit         223MB     buildkit.dockerfile.v0
<missing>      7 minutes ago    RUN /bin/sh -c yarn generate # buildkit         17MB      buildkit.dockerfile.v0
<missing>      7 minutes ago    RUN /bin/sh -c yarn install --production --f…   4.18GB    buildkit.dockerfile.v0
<missing>      24 minutes ago   COPY . . # buildkit                             1.15MB    buildkit.dockerfile.v0
<missing>      24 minutes ago   COPY package.json yarn.lock ./ # buildkit       414kB     buildkit.dockerfile.v0
<missing>      38 minutes ago   WORKDIR /app                                    0B        buildkit.dockerfile.v0
<missing>      8 days ago       CMD ["node"]                                    0B        buildkit.dockerfile.v0
<missing>      8 days ago       ENTRYPOINT ["docker-entrypoint.sh"]             0B        buildkit.dockerfile.v0
<missing>      8 days ago       COPY docker-entrypoint.sh /usr/local/bin/ # …   388B      buildkit.dockerfile.v0
<missing>      8 days ago       RUN /bin/sh -c apk add --no-cache --virtual …   5.59MB    buildkit.dockerfile.v0
<missing>      8 days ago       ENV YARN_VERSION=1.22.22                        0B        buildkit.dockerfile.v0
<missing>      8 days ago       RUN /bin/sh -c addgroup -g 1000 node     && …   118MB     buildkit.dockerfile.v0
<missing>      8 days ago       ENV NODE_VERSION=20.16.0                        0B        buildkit.dockerfile.v0
<missing>      10 days ago      /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B        
<missing>      10 days ago      /bin/sh -c #(nop) ADD file:a71f7e9bc66668361…   8.83MB    

The Problem

From the RUN /bin/sh -c yarn install --production --f… 4.18GB buildkit.dockerfile.v0 line I can see that most of that bloat comes from the yarn install command

But why would the frontend at a tiny size?

inspecting a running backend container

With a bit of prodding, I find that the yarn cache is gigantic

cd /app 
du -sh /usr/local/share/.cache/yarn/v6/

3.2G

WOH, even with my node_modules installed, the local dev directory does not even reach that size.

I'm dumb

I overlooked the line FROM builder AS runner. It should have been FROM base AS runner as to not copy over any files from the build step